﻿using MyK3CloudCore.IOC.Attributes;
using SQLBuilder.Entry;
using SQLBuilder.Enums;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.Json;
using System.Text.RegularExpressions;

namespace MyK3CloudCore.IOC.Extensions;

/// <summary>
/// 简单实现 根据Layui扩展组件dynamicCondition的Json生成表达式
/// </summary>
public static class ExpressionBuilder
{
    /// <summary>
    /// IN
    /// </summary>
    private static readonly string In = nameof(In).ToLower();
    /// <summary>
    /// Not IN
    /// </summary>
    private static readonly string NotIn = nameof(NotIn).ToLower();
    /// <summary>
    /// 等于
    /// </summary>
    private static readonly string Equal = nameof(Equal).ToLower();
    /// <summary>
    /// 不等于
    /// </summary>
    private static readonly string Unequal = nameof(Unequal).ToLower();
    /// <summary>
    /// 包含
    /// </summary>
    private static readonly string Like = nameof(Like).ToLower();
    /// <summary>
    /// 左包含
    /// </summary>
    private static readonly string Start = nameof(Start).ToLower();
    /// <summary>
    /// 右包含
    /// </summary>
    private static readonly string End = nameof(End).ToLower();
    /// <summary>
    /// 不包含
    /// </summary>
    private static readonly string NotLike = nameof(NotLike).ToLower();
    /// <summary>
    /// 为空
    /// </summary>
    private static readonly string Empty = nameof(Empty).ToLower();
    /// <summary>
    /// 不为空
    /// </summary>
    private static readonly string NotEmpty = nameof(NotEmpty).ToLower();
    /// <summary>
    /// 范围
    /// </summary>
    private static readonly string Between = nameof(Between).ToLower();
    /// <summary>
    /// 大于
    /// </summary>
    private static readonly string Greater_than = nameof(Greater_than).ToLower();
    /// <summary>
    /// 小于
    /// </summary>
    private static readonly string Less_than = nameof(Less_than).ToLower();
    /// <summary>
    /// 并且
    /// </summary>
    private static readonly string And = nameof(And).ToLower();
    /// <summary>
    /// 字符类型
    /// </summary>
    private static readonly Type StringType = typeof(string);
    private static readonly Type[] paramsType = new Type[] { StringType };

    private delegate Expression Binder(Expression left, Expression right);
    /// <summary>
    /// 根据 https://github.com/JeremyLikness/ExpressionGenerator 修改
    /// </summary>
    private static Expression? ParseTree<T>(JsonElement condition, ParameterExpression parm)
    {
        Expression? left = null;
        Type t = typeof(T);
        foreach (var rule in condition.EnumerateArray())
        {
            string logicalOperator = rule.GetProperty(nameof(logicalOperator)).GetString();
            // https://gitee.com/dotnetchina/SqlSugar/issues/I4E3NU
            Binder binder = logicalOperator == And ? Expression.AndAlso : Expression.OrElse;
            Expression? bind(Expression? left, Expression right) => left == null ? right : binder(left, right);

            string conditionFieldVal = rule.GetProperty(nameof(conditionFieldVal)).GetString();
            PropertyInfo propertyAny = t.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.Name.Equals(conditionFieldVal, StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
            if (propertyAny == null) { continue; }
            /// 比较操作符
            string conditionOptionVal = rule.GetProperty(nameof(conditionOptionVal)).GetString();
            JsonElement conditionValueVal = rule.GetProperty(nameof(conditionValueVal));
            JsonElement conditionValueLeftVal = rule.GetProperty(nameof(conditionValueLeftVal));
            JsonElement conditionValueRightVal = rule.GetProperty(nameof(conditionValueRightVal));
            conditionValueVal.TryGetProperty("value", out JsonElement value);
            conditionValueLeftVal.TryGetProperty("value", out JsonElement leftValue);
            conditionValueRightVal.TryGetProperty("value", out JsonElement rightValue);
            //var property = Expression.Property(parm, propertyAny);
            var property = Expression.PropertyOrField(parm, propertyAny.Name);
            if (conditionOptionVal == In)
            {
                dynamic? Value = default;
                if (propertyAny.PropertyType == typeof(string))
                {
                    Value = ExpTmpField<string>.Parse(value.GetString());
                }
                else if (propertyAny.PropertyType == typeof(int))
                {
                    Value = ExpTmpField<int>.Parse(value.GetString());
                }
                else if (propertyAny.PropertyType == typeof(double))
                {
                    Value = ExpTmpField<double>.Parse(value.GetString());
                }
                else
                {
                    throw new NotImplementedException();
                }
                if (Value.Length == 0) { continue; }
                MethodInfo MethodContains = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
                    .Single(m => m.Name == nameof(Enumerable.Contains) && m.GetParameters().Length == 2).MakeGenericMethod(propertyAny.PropertyType);
                var toCompare = Expression.Constant(Value);
                FieldInfo fieldInfo = Value.ArrFieldInfo();
                var right = Expression.Call(null, MethodContains, new Expression[2] { Expression.Field(toCompare, fieldInfo), property });
                left = bind(left, right);

            }
            else if (conditionOptionVal == NotIn)
            {
                dynamic? Value = default;
                if (propertyAny.PropertyType == typeof(string))
                {
                    Value = ExpTmpField<string>.Parse(value.GetString());
                }
                else if (propertyAny.PropertyType == typeof(int))
                {
                    Value = ExpTmpField<int>.Parse(value.GetString());
                }
                else if (propertyAny.PropertyType == typeof(double))
                {
                    Value = ExpTmpField<double>.Parse(value.GetString());
                }
                else
                {
                    throw new NotImplementedException();
                }
                if (Value.Length == 0) { continue; }
                var toCompare = Expression.Constant(Value);
                FieldInfo fieldInfo = Value.ArrFieldInfo();
                MethodInfo MethodContains = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
                    .Single(m => m.Name == nameof(Enumerable.Contains) && m.GetParameters().Length == 2).MakeGenericMethod(propertyAny.PropertyType);
                var right = Expression.Not(Expression.Call(null, MethodContains, new Expression[2] { Expression.Field(toCompare, fieldInfo), property }));
                left = bind(left, right);
            }
            else if (conditionOptionVal == Like)
            {
                var toCompare = Expression.Constant(value.GetString());
                MethodInfo contains = StringType.GetMethod(nameof(Enumerable.Contains), paramsType);
                var right = Expression.Call(property, contains, toCompare);
                left = bind(left, right);
            }
            else if (conditionOptionVal == NotLike)
            {
                var toCompare = Expression.Constant(value.GetString());
                MethodInfo contains = StringType.GetMethod(nameof(Enumerable.Contains), paramsType);
                var right = Expression.Not(Expression.Call(property, contains, toCompare));
                left = bind(left, right);
            }
            else if (conditionOptionVal == Start)
            {
                var toCompare = Expression.Constant(value.GetString());
                MethodInfo contains = StringType.GetMethod("StartsWith", paramsType);
                var right = Expression.Call(property, contains, toCompare);
                left = bind(left, right);
            }
            else if (conditionOptionVal == End)
            {
                var toCompare = Expression.Constant(value.GetString());
                MethodInfo contains = StringType.GetMethod("EndsWith", paramsType);
                var right = Expression.Call(property, contains, toCompare);
                left = bind(left, right);
            }
            else if (conditionOptionVal == Unequal)
            {
                var rightProperty = ToPropertyExpression(parm, value.GetString());
                if (rightProperty != null)
                {
                    var right = Expression.NotEqual(property, rightProperty);
                    left = bind(left, right);
                }
                else
                {
                    object propertyValue;
                    if (propertyAny.PropertyType.IsEnum)
                    {
                        Enum.TryParse(propertyAny.PropertyType.ParseNullableToType(), value.GetString(), true, out object result);
                        propertyValue = result;
                    }
                    else
                    {
                        propertyValue = Convert.ChangeType(value.GetString(), propertyAny.PropertyType.ParseNullableToType());
                    }
                    var toCompare = Expression.Constant(propertyValue, propertyAny.PropertyType);
                    var right = Expression.NotEqual(property, toCompare);
                    left = bind(left, right);
                }
            }
            else if (conditionOptionVal == Empty)
            {
                //var right = Expression.Equal(property, Expression.Constant(null));
                MethodInfo IsNullOrEmpty = StringType.GetMethod("IsNullOrEmpty", paramsType);
                var right = Expression.Call(null, IsNullOrEmpty, property);
                left = bind(left, right);
            }
            else if (conditionOptionVal == NotEmpty)
            {
                //var right = Expression.NotEqual(property, Expression.Constant(null));
                MethodInfo IsNullOrEmpty = StringType.GetMethod("IsNullOrEmpty", paramsType);
                var right = Expression.Not(Expression.Call(null, IsNullOrEmpty, property));
                left = bind(left, right);
            }
            else if (conditionOptionVal == Between)
            {
                if (!string.IsNullOrEmpty(leftValue.ValueKind != JsonValueKind.Undefined ? leftValue.GetString() : null))
                {
                    object propertyValue = Convert.ChangeType(leftValue.GetString(), propertyAny.PropertyType.ParseNullableToType());
                    if (propertyValue is DateTime dateSame)
                    {
                        propertyValue = dateSame.Date;
                    }
                    var toCompare = Expression.Constant(propertyValue, propertyAny.PropertyType);
                    var right = Expression.GreaterThanOrEqual(property, toCompare);
                    left = bind(left, right);
                }
                if (!string.IsNullOrEmpty(rightValue.ValueKind != JsonValueKind.Undefined ? rightValue.GetString() : null))
                {
                    var propertyValue = Convert.ChangeType(rightValue.GetString(), propertyAny.PropertyType.ParseNullableToType());
                    if (propertyValue is DateTime dateSame)
                    {
                        propertyValue = dateSame.Date.AddDays(1).AddSeconds(-1);
                    }
                    var toCompare = Expression.Constant(propertyValue, propertyAny.PropertyType);
                    var right = Expression.LessThanOrEqual(property, toCompare);
                    left = bind(left, right);
                }
            }
            else if (conditionOptionVal == Greater_than)
            {
                var rightProperty = ToPropertyExpression(parm, value.GetString());
                if (rightProperty != null)
                {
                    var right = Expression.GreaterThan(property, rightProperty);
                    left = bind(left, right);
                }
                else
                {
                    var propertyValue = Convert.ChangeType(value.GetString(), propertyAny.PropertyType.ParseNullableToType());
                    if (propertyValue is DateTime dateSame)
                    {
                        propertyValue = dateSame.Date.AddDays(1).AddSeconds(-1);
                    }
                    var toCompare = Expression.Constant(propertyValue, propertyAny.PropertyType);
                    var right = Expression.GreaterThan(property, toCompare);
                    left = bind(left, right);
                }
            }
            else if (conditionOptionVal == Less_than)
            {
                var rightProperty = ToPropertyExpression(parm, value.GetString());
                if (rightProperty != null)
                {
                    var right = Expression.LessThan(property, rightProperty);
                    left = bind(left, right);
                }
                else
                {
                    var propertyValue = Convert.ChangeType(value.GetString(), propertyAny.PropertyType.ParseNullableToType());
                    if (propertyValue is DateTime dateSame)
                    {
                        propertyValue = dateSame.Date;
                    }
                    var toCompare = Expression.Constant(propertyValue, propertyAny.PropertyType);
                    var right = Expression.LessThan(property, toCompare);
                    left = bind(left, right);
                }
            }
            else if (conditionOptionVal == Equal)
            {
                var rightProperty = ToPropertyExpression(parm, value.GetString());
                if (rightProperty != null)
                {
                    var right = Expression.Equal(property, rightProperty);
                    left = bind(left, right);
                }
                else
                {
                    object propertyValue;
                    if (propertyAny.PropertyType.IsEnum)
                    {
                        Enum.TryParse(propertyAny.PropertyType.ParseNullableToType(), value.GetString(), true, out object result);
                        propertyValue = result;
                    }
                    else
                    {
                        propertyValue = Convert.ChangeType(value.GetString(), propertyAny.PropertyType.ParseNullableToType());
                    }
                    if (propertyValue is DateTime dataSame)
                    {
                        propertyValue = dataSame.Date;
                        var toCompare = Expression.Constant(propertyValue, propertyAny.PropertyType);
                        var right = Expression.GreaterThanOrEqual(property, toCompare);
                        left = bind(left, right);
                        propertyValue = dataSame.Date.AddDays(1).AddSeconds(-1);
                        toCompare = Expression.Constant(propertyValue, propertyAny.PropertyType);
                        right = Expression.LessThanOrEqual(property, toCompare);
                        left = bind(left, right);
                    }
                    else
                    {
                        var toCompare = Expression.Constant(propertyValue, propertyAny.PropertyType);
                        var right = Expression.Equal(property, toCompare);
                        left = bind(left, right);
                    }
                }
            }
            else
            {
                throw new NotImplementedException();
            }
        }
        return left;
    }

    /// <summary>
    /// 根据Json生成表达式
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="doc"></param>
    /// <returns></returns>
    public static Expression<Func<T, bool>> ParseExpressionOf<T>(JsonDocument doc)
    {
        var itemExpression = Expression.Parameter(typeof(T));
        var conditions = ParseTree<T>(doc.RootElement, itemExpression);
        if (conditions == null) { return t => true; }
        if (conditions.CanReduce)
        {
            conditions = conditions.ReduceAndCheck();
        }
        var query = Expression.Lambda<Func<T, bool>>(conditions, itemExpression);
        return query;
    }

    public static Func<T, bool> ParsePredicateOf<T>(JsonDocument doc)
    {
        var query = ParseExpressionOf<T>(doc);
        return query.Compile();
    }

    [DebuggerStepThrough]
    public static string GetFilterString<T>(Expression<Func<T, bool>>? expression) where T : class
    {
        if (expression == null) { return ""; }
        var fields = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(t => t.GetCustomAttributes(typeof(AliasAttribute), true).Length > 0).Select(t => new
        {
            t.Name,
            AliasName = t.GetCustomAttribute<AliasAttribute>().Name
        });
        bool hasWhere = false;
        var sql = new SqlBuilderCore<T>(DatabaseType.SqlServer, (sql, parameter) =>
        {
            foreach (var item in (Dictionary<string, object>)parameter)
            {
                var type = item.Value.GetType();
                bool b = type == typeof(string);
                bool d = type == typeof(DateTime);
                if (b || d)
                {
                    sql = sql.Replace(item.Key, "'" + item.Value.ToString() + "'");
                }
                else
                {
                    sql = sql.Replace(item.Key, item.Value.ToString());
                }
            }

            //修改原sql
            sql = sql.Replace("t.", "");
            sql = sql.Replace("AS t", "");
            sql = sql.Replace("' + '%'", "%'");
            sql = sql.Replace("'%' + '", "'%");
            sql = sql.Replace("__", ".");
            foreach (var item in fields)
            {
                sql = sql.Replace(item.Name, item.AliasName);
            }
            return sql;
        }, false).Where(expression, ref hasWhere).Sql;
        if (hasWhere)
        {
            return sql.Split("WHERE")[1].TrimStart().TrimEnd();
        }
        return "";
    }

    [DebuggerStepThrough]
    public static string GetOrderString<T>(Expression<Func<T, object>>? expression, params OrderType[]? orders) where T : class
    {
        if (expression == null) { return ""; }
        var fields = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(t => t.GetCustomAttributes(typeof(AliasAttribute), true).Length > 0).Select(t => new
        {
            t.Name,
            AliasName = t.GetCustomAttribute<AliasAttribute>().Name
        });
        return new SqlBuilderCore<T>(DatabaseType.SqlServer, (sql, parameter) =>
        {
            foreach (var item in fields)
            {
                sql = sql.Replace(item.Name, item.AliasName);
            }
            sql = sql.Replace("__", ".");
            sql = sql.Replace("ORDER BY", "");
            return sql.TrimStart().TrimEnd();
        }, false).OrderBy(expression, orders).Sql;
    }
    /// <summary>
    /// 特定规则字符转换成属性,用于属性与属性之间比较
    /// </summary>
    /// <param name="name">双花括号‘{{}}’包裹的字符</param>
    /// <returns></returns>
    private static MemberExpression? ToPropertyExpression(ParameterExpression param, string name)
    {
        var re = new Regex(@"{{([\w\s-_.]+)}}");
        if (re.IsMatch(name))
        {
            var pf = param.Type.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(x => x.Name.Equals(name[2..^2], StringComparison.OrdinalIgnoreCase)).FirstOrDefault();
            if (pf == null) return null;
            return Expression.Property(param, pf);
        }
        return null;
    }
}
/// <summary>
/// 临时赋值使用
/// </summary>
[DebuggerStepThrough]
public class ExpTmpField<T>
{
    public T[] Arr;
    /// <summary>
    /// 数组长度，用于判断是否为空值
    /// </summary>
    public int Length { get; set; }
    private ExpTmpField(string arr)
    {
        Arr = ParseToArray<T>(arr);
        Length = Arr.Length;
    }
    public static ExpTmpField<T> Parse(string arr)
    {
        return new(arr);
    }
    public FieldInfo ArrFieldInfo()
    {
        return FieldInfo.GetFieldFromHandle(GetType().GetField(nameof(Arr)).FieldHandle, GetType().TypeHandle);
    }
    private static T1[] ParseToArray<T1>(string obj)
    {
        if (!string.IsNullOrEmpty(obj))
        {
            var arrStr = obj.Split(",", StringSplitOptions.RemoveEmptyEntries);
            var arr_T1 = new T1[arrStr.Length];
            for (int i = 0; i < arrStr.Length; i++)
            {
                try { arr_T1[i] = (T1)Convert.ChangeType(arrStr[i], typeof(T1)); } catch { }
            }
            return arr_T1.Distinct().ToArray();
        }
        return Array.Empty<T1>();
    }
}